{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Building interactive plots using `bqplot` and `ipywidgets`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* `bqplot` is built on top of the `ipywidgets` framework\n",
    "* `ipwidgets` and `bqplot` widgets can be seamlessly integrated to build interactive plots\n",
    "* `bqplot` figure widgets can be stacked with UI controls available in `ipywidgets` by using `Layout` classes (Box, HBox, VBox) in `ipywidgets`\n",
    "(Note that *only* `Figure` objects (not `Mark` objects) inherit from `DOMWidget` class and can be combined with other widgets from `ipywidgets`)\n",
    "* Trait attributes of widgets can be linked using callbacks. Callbacks should be registered using the `observe` method\n",
    "\n",
    "Please follow these links for detailed documentation on:\n",
    "1. [Layout and Styling of Jupyter Widgets](https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Styling.html)\n",
    "*  [Linking Widgets](https://ipywidgets.readthedocs.io/en/stable/examples/Widget%20Events.html)\n",
    "\n",
    "<br>Let's look at examples of linking plots with UI controls"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import ipywidgets as widgets\n",
    "import bqplot.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Update the plot on a button click"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y = np.random.randn(100).cumsum() # simple random walk\n",
    "\n",
    "# create a button\n",
    "update_btn = widgets.Button(description='Update', button_style='success')\n",
    "\n",
    "# create a figure widget\n",
    "fig1 = plt.figure(animation_duration=750)\n",
    "line = plt.plot(y)\n",
    "\n",
    "# define an on_click function\n",
    "def on_btn_click(btn):\n",
    "    # update the y attribute of line mark\n",
    "    line.y = np.random.randn(100).cumsum() # another random walk\n",
    "\n",
    "# register the on_click function\n",
    "update_btn.on_click(on_btn_click)\n",
    "    \n",
    "# stack button and figure using VBox\n",
    "widgets.VBox([fig1, update_btn])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's look at an example where we link a plot to a dropdown menu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "# create a dummy time series for 5 dummy stock tickers\n",
    "dates = pd.date_range(start='20180101', end='20181231')\n",
    "n = len(dates)\n",
    "tickers = list('ABCDE')\n",
    "prices = pd.DataFrame(np.random.randn(n, 5).cumsum(axis=0), columns=tickers)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# create a dropdown menu for tickers\n",
    "dropdown = widgets.Dropdown(description='Ticker', options=tickers)\n",
    "\n",
    "# create figure for plotting time series\n",
    "current_ticker = dropdown.value\n",
    "fig_title_tmpl = '\"{}\" Time Series' # string template for title of the figure \n",
    "fig2 = plt.figure(title=fig_title_tmpl.format(current_ticker))\n",
    "fig2.layout.width = '900px'\n",
    "time_series = plt.plot(dates, prices[current_ticker])\n",
    "plt.xlabel('Date')\n",
    "plt.ylabel('Price')\n",
    "\n",
    "# 1. create a callback which updates the plot when dropdown item is selected\n",
    "def update_plot(*args):\n",
    "    selected_ticker = dropdown.value\n",
    "    \n",
    "    # update the y attribute of the mark by selecting \n",
    "    # the column from the price data frame\n",
    "    time_series.y = prices[selected_ticker]\n",
    "    \n",
    "    # update the title of the figure\n",
    "    fig2.title = fig_title_tmpl.format(selected_ticker)\n",
    "\n",
    "# 2. register the callback by using the 'observe' method\n",
    "dropdown.observe(update_plot, 'value')\n",
    "\n",
    "# stack the dropdown and fig widgets using VBox\n",
    "widgets.VBox([dropdown, fig2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's now create a scatter plot where we select X and Y data from the two dropdown menus"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# create two dropdown menus for X and Y attributes of scatter\n",
    "x_dropdown = widgets.Dropdown(description='X', options=tickers, value='A')\n",
    "y_dropdown = widgets.Dropdown(description='Y', options=tickers, value='B')\n",
    "\n",
    "# create figure for plotting the scatter\n",
    "x_ticker = x_dropdown.value\n",
    "y_ticker = y_dropdown.value\n",
    "\n",
    "# set up fig_margin to allow space to display color bar\n",
    "fig_margin = dict(top=20, bottom=40, left=60, right=80)\n",
    "fig3 = plt.figure(animation_duration=1000, fig_margin=fig_margin)\n",
    "\n",
    "# custom axis options for color data\n",
    "axes_options = {'color': {'tick_format': '%m/%y', \n",
    "                          'side': 'right',\n",
    "                          'num_ticks': 5}}\n",
    "scatter = plt.scatter(x=prices[x_ticker], \n",
    "                      y=prices[y_ticker],\n",
    "                      color=dates, # represent chronology using color scale\n",
    "                      stroke='black',\n",
    "                      colors=['red'],\n",
    "                      default_size=32,\n",
    "                      axes_options=axes_options)\n",
    "plt.xlabel(x_ticker)\n",
    "plt.ylabel(y_ticker)\n",
    "\n",
    "# 1. create a callback which updates the plot when dropdown item is selected\n",
    "def update_scatter(*args):\n",
    "    x_ticker = x_dropdown.value\n",
    "    y_ticker = y_dropdown.value\n",
    "    \n",
    "    # update the x and y attributes of the mark by selecting\n",
    "    # the column from the price data frame\n",
    "    with scatter.hold_sync():\n",
    "        scatter.x = prices[x_ticker]\n",
    "        scatter.y = prices[y_ticker]\n",
    "    \n",
    "    # update the title of the figure\n",
    "    plt.xlabel(x_ticker)\n",
    "    plt.ylabel(y_ticker)\n",
    "\n",
    "# 2. register the callback by using the 'observe' method\n",
    "x_dropdown.observe(update_scatter, 'value')\n",
    "y_dropdown.observe(update_scatter, 'value')\n",
    "\n",
    "# stack the dropdown and fig widgets using VBox\n",
    "widgets.VBox([widgets.HBox([x_dropdown, y_dropdown]), fig3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the example below, we'll look at plots of trigonometic functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "funcs = dict(sin=np.sin, cos=np.cos, tan=np.tan, sinh=np.sinh, tanh=np.tanh)\n",
    "dropdown = widgets.Dropdown(options=funcs, description='Function')\n",
    "\n",
    "fig = plt.figure(title='sin(x)', animation_duration=1000)\n",
    "\n",
    "# create x and y data attributes for the line chart\n",
    "x = np.arange(-10, 10, .1)\n",
    "y = np.sin(x)\n",
    "\n",
    "line = plt.plot(x, y ,'m')\n",
    "\n",
    "def update_line(*args):\n",
    "    f = dropdown.value\n",
    "    fig.title = f'{f.__name__}(x)'\n",
    "    line.y = f(line.x)\n",
    "    \n",
    "dropdown.observe(update_line, 'value')\n",
    "\n",
    "widgets.VBox([dropdown, fig])"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
